using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.UI;
using XLua;

//lua脚本管理器：虚拟机注册回调，
public class LuaManager : MonoBehaviour
{
    //所有的lua文件名(assetname)，在解析版本信息时添加元素
    public List<string> LuaNames = new List<string>();
    //缓存lua脚本的内容(m_代表缓存)，缓存和别的变量有什么区别？？？？？？？？？？？？？？？？？
    private Dictionary<string, byte[]> m_LuaScripts;
    //lua虚拟机消耗比较大，因此全局用这一个
    public LuaEnv LuaEnv = new LuaEnv();
     
    private void Awake()
    {
        //初始化lua虚拟机
        //给lua虚拟机注册回调，通过该虚拟机在lua代码里头调用require时，参数将会透传给回调，回调中就可以根据这个参数去加载指定文件
        LuaEnv.AddLoader(Loader);
    }

    //这种外部声明的全局变量配合上init里面的使用方式
    //是可能会会有其他在地方添加回调
    //以及在一个函数中添加，另一个函数中使用

    //初始化函数：在我执行lua脚本前的操作
    /// <summary>
    /// 根据游戏模式进行lua脚本的初始化加载，添加到缓存以便后续调用
    /// </summary>
    /// <param name="init">回调函数：用于加载完lua脚本</param>
    public void Init()
    {
        LuaEnv.AddBuildin("rapidjson", XLua.LuaDLL.Lua.LoadRapidJson);
        //为进行缓存的字典初始化
        m_LuaScripts = new Dictionary<string, byte[]>();
        //根据操作模式进行脚本的初始化（从resources中加载 / 从ab包资源路径加载——其中，如果为package模式则在只读目录加载，为更新模式则在读写目录加载）
        //如果不写这个条件编译指令的话，播放可能没问题，但是在打包时会报编译错误，
        //因为打包时会编译脚本，单独复制一个Assembly-CSharp.dll到另外一个地方，而#if UNITY_EDITOR中的代码是被编译到Assembly-CSharp-Editor中的，
        //因此EditorLoadLuaScript()会报编译错误
#if UNITY_EDITOR
        if (AppConst.GameMode == GameMode.EditorMode)
            EditorLoadLuaScript();
        else
#endif
        {
            LoadLuaScript();
        }

    }

    public void StartLua(string name)
    {
        LuaEnv.DoString(string.Format("require '{0}'", name));
    }

    //封装后的回调（loader）
    private byte[] Loader(ref string name)
    {
        return GetLuaScript(name);
    }

    //回调（loader）：处理路径为assetname，并得到字典中缓存的二进制文件
    public byte[] GetLuaScript(string name)
    {
        //方便使用，我们传入名称的时候一般习惯于传“.”，因此需要处理
        //不会把后缀改错吗？不会，直接传入脚本名，GetLuaPath进行后缀添加
        name = name.Replace(".", "/");
        string filename = PathUtil.GetLuaPath(name);

        byte[] luaScript = null;
        if (!m_LuaScripts.TryGetValue(filename, out luaScript))
        {
            Debug.LogError("lua脚本不存在:" + filename);
        }
        return luaScript;
    }

    /// <summary>
    /// 游戏模式下，通过遍历LuaNames异步加载所有bundle包中的lua脚本，并加载到缓存，全部加载完成后释放LuaNames
    /// </summary>
    public void LoadLuaScript()
    {
        foreach (string name in LuaNames)
        {
            //通过Manager统一调用ResourceManager类中得方法，传入name以及匿名函数作为回调函数
            Manager.Resource.LoadLua(name, (UnityEngine.Object obj) =>
            {
                //lua脚本识别为文本资源，并取出其中的原始byte（二进制）数据。将键值对添加到字典（缓存）
                AddLuaScripts(name, (obj as TextAsset).bytes);
                //使用完后
                if(m_LuaScripts.Count >= LuaNames.Count)
                {
                    //所有lua文件加载缓存完成的时候
                    //告诉外部我已经完成了全部加载
                    Manager.Event.Fire((int)GameEvent.StartLua);
                    LuaNames.Clear();
                    LuaNames = null;
                }
            });
        }
    }

    //条件编译指令，它指示编译器仅在 Unity 编辑器环境中编译和执行该代码块。
#if UNITY_EDITOR
    //编辑器模式下得全部lua脚本加载到缓存
    //与LoadLuaScript不同的是：
    //1.这里加载的文件是在LuaPath中加载，即resources目录中加载，而不是通过bundle目录中加载。免于重复打包和读取包的操作。
    //2.不需要使用LoadLua这种异步加载ab包和资源的方法，因为没有打包，直接使用即可
    void EditorLoadLuaScript()
    {
        string[] luaFiles = Directory.GetFiles(PathUtil.LuaPath, "*.bytes", SearchOption.AllDirectories);
        for (int i = 0; i < luaFiles.Length; i++)
        {
            //标准化，\\换成/
            string fileName = PathUtil.GetStandardPath(luaFiles[i]);
            //读取文件
            byte[] file = File.ReadAllBytes(fileName);
            //键的名称改为相对路径
            AddLuaScripts(PathUtil.GetUnityPath(fileName), file);
        }
        Manager.Event.Fire((int)GameEvent.StartLua);
    }
#endif

    //脚本名与二进制数据的键值对添加到缓存
    private void AddLuaScripts(string assetName, byte[] luaScript)
    {
        //这样写如果键重复会报错，需要先判断一下
        //m_LuaScripts.Add(assetName, luaScript);
        m_LuaScripts[assetName] = luaScript;
    }

    private void Update()
    {
        if (LuaEnv != null)
        {
            //每一帧调用，释放lua内存，垃圾回收
            //也可以写个计时器隔一段时间调用
            LuaEnv.Tick();
        }
    }

    //脚本关闭的时候销毁lua虚拟机
    private void OnDestroy()
    {
        if (m_LuaScripts != null)
        {
            //虽然在c#中事件绑定的lua函数都清空了，但是在lua脚本中还绑定了按钮相关的事件
            //此时lua虚拟机已经关了，但是lua脚本对于c#事件的监听还没有清空，因此会报错（）
            //因为ui在后期更新会变，因此button
            LuaEnv.Dispose();
            LuaEnv = null;
        }
    }

}
